package com.jieao.framework.interceptor;

import com.jieao.common.core.domain.BaseEntity;
import com.jieao.framework.util.ShiroUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * @Author LWF
 * @Date2021/3/31 11:41
 **/

@Intercepts
        ({
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        })
@Component
public class MybatisUpdateInterceptor implements Interceptor {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    // 这是对应上面的args的序号
    public static final Integer INDEX_ZERO = 0;
    public static final Integer INDEX_ONE = 1;
    public static final String CREATE_TIME_COLUMN = "create_time";
    public static final String CREATE_TIME_FEILD = "createTime";
    public static final String CREATE_BY_FEILD = "createBy";
    public static final String CREATE_BY_COLUMN = "create_by";
    public static final String UPDATE_BY_COLUMN = "update_by";
    public static final String UPDATE_BY_FEILD = "updateBy";
    public static final String UPDATE_TIME_FEILD = "updateTime";
    public static final String UPDATE_TIME_COLUMN = "update_time";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("进入公共拦截器");
        // 获取参数
        Object[] queryArgs = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) queryArgs[INDEX_ZERO];
        Object object = queryArgs[INDEX_ONE];
        // 获取sql
        BoundSql boundSql = mappedStatement.getBoundSql(object);
        String sql = boundSql.getSql().toLowerCase();
        //删除sql不处理
        if (sql.indexOf("delete") != -1) {
            return invocation.proceed();
        }
        //未继承BaseEntity不处理
        Class<?> superclass = object.getClass().getSuperclass();

        if (null == superclass || !superclass.getName().equals(BaseEntity.class.getName())) {
            return invocation.proceed();
        }

        // 获取sql参数列表
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        // 根据sql类型对应不同操作
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (SqlCommandType.INSERT.equals(sqlCommandType)) {
            // 拼接预编译sql、参数
            sql = appendInsertSqlAndAutoFildInsertFeild(sql, object, mappedStatement, parameterMappings);
        }
        if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // 拼接预编译sql、参数
            sql = appendUpdateSqlAndAutoFildUpdateFeild(sql, object, mappedStatement, parameterMappings);
        }

        // 重新new一个查询语句对像
        BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql, parameterMappings, boundSql.getParameterObject());
        // 把新的查询放到statement里
        MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
        queryArgs[INDEX_ZERO] = newMs;
        return invocation.proceed();
    }

    private String appendInsertSqlAndAutoFildInsertFeild(String sql, Object object,
                                                         MappedStatement mappedStatement, List<ParameterMapping> parameterMappings) throws IllegalAccessException, NoSuchFieldException {
        if (!sql.contains(CREATE_TIME_COLUMN)) {
            sql = orgSql(sql, CREATE_TIME_COLUMN);
            setFeildValue(object, CREATE_TIME_FEILD, new Date());
            parameterMappings.add(newParameterMapping(mappedStatement.getConfiguration(), CREATE_TIME_FEILD, Date.class));
        }
        if (!sql.contains(CREATE_BY_COLUMN)) {
            sql = orgSql(sql, CREATE_BY_COLUMN);
            setFeildValue(object, CREATE_BY_FEILD, ShiroUtils.getUserId().toString());
            parameterMappings.add(newParameterMapping(mappedStatement.getConfiguration(), CREATE_BY_FEILD, String.class));
        }
        if (!sql.contains(UPDATE_TIME_COLUMN)) {
            sql = orgSql(sql, UPDATE_TIME_COLUMN);
            setFeildValue(object, UPDATE_TIME_FEILD, new Date());
            parameterMappings.add(newParameterMapping(mappedStatement.getConfiguration(), UPDATE_TIME_FEILD, Date.class));
        }
        if (!sql.contains(UPDATE_BY_COLUMN)) {
            sql = orgSql(sql, UPDATE_BY_COLUMN);
            setFeildValue(object, UPDATE_BY_FEILD, ShiroUtils.getUserId().toString());
            parameterMappings.add(newParameterMapping(mappedStatement.getConfiguration(), UPDATE_BY_FEILD, String.class));
        }
        return sql;
    }

    private String appendUpdateSqlAndAutoFildUpdateFeild(String sql, Object object,
                                                         MappedStatement mappedStatement, List<ParameterMapping> parameterMappings) throws IllegalAccessException, NoSuchFieldException {
        if (!sql.contains(UPDATE_TIME_COLUMN)) {
            sql = orgUpdateSql(sql, UPDATE_TIME_COLUMN);
            setFeildValue(object, UPDATE_TIME_FEILD, new Date());
            parameterMappings.add(0, newParameterMapping(mappedStatement.getConfiguration(), UPDATE_TIME_FEILD, Date.class));
        }
        if (!sql.contains(UPDATE_BY_COLUMN)) {
            sql = orgUpdateSql(sql, UPDATE_BY_COLUMN);
            setFeildValue(object, UPDATE_BY_FEILD, ShiroUtils.getUserId().toString());
            parameterMappings.add(0, newParameterMapping(mappedStatement.getConfiguration(), UPDATE_BY_FEILD, String.class));
        }
        return sql;
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    private ParameterMapping newParameterMapping(Configuration configuration, String property, Class<?> javaType) {
        return new ParameterMapping.Builder(configuration, property, javaType).build();
    }

    private void setFeildValue(Object object, String filedName, Object value) throws IllegalAccessException, NoSuchFieldException {
        Field field;
        try {
            field = object.getClass().getSuperclass().getDeclaredField(filedName);
        } catch (NoSuchFieldException e) {
            throw new NoSuchFieldException("没有字段" + filedName);
        }
        field.setAccessible(true);
        //  获取字段类型，根据字段类型赋值
        field.set(object, value);
    }

    private String orgUpdateSql(String sql, String param) {
        String newSql = new StringBuffer().append(sql, 0, sql.indexOf(" set ") + 4)
                .append(" ")
                .append(param).append("=?, ")
                .append(sql.substring(sql.indexOf(" set ") + 4)).toString();
        return newSql;
    }

    private String orgSql(String sql, String param) {
        StringBuffer sb = new StringBuffer();
        sb.append(sql, 0, sql.indexOf(")"))
                .append(",").append(param)
                .append(sql, sql.indexOf(")"), sql.lastIndexOf(")")).append(",?)");
        return sb.toString();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    public class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}

